package com.aiwiown.snackmq.storage.index;

import com.aiwiown.snackmq.storage.util.BufferCleaner;
import com.aiwiown.snackmq.storage.util.ResourceManager;
import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 索引文件，用于快速定位消息。
 * 每个索引条目包含逻辑偏移量和物理位置。
 */
@Slf4j
public class Index implements Closeable {

    private final File indexFile;
    private final RandomAccessFile randomAccessFile;
    private FileChannel fileChannel;
    // 【最终修复】将 MappedByteBuffer 的初始化延迟，避免在构造时就锁定文件
    private volatile MappedByteBuffer mappedBuffer;
    private final long maxSize;
    private final AtomicInteger entryCount = new AtomicInteger(0);
    private final AtomicBoolean closed = new AtomicBoolean(false);
    private final Object mapLock = new Object(); // 用于同步映射操作的锁

    public Index(File indexFile, long maxSize) throws IOException {
        this.indexFile = indexFile;
        this.maxSize = maxSize;
        boolean fileExisted = indexFile.exists();

        this.randomAccessFile = new RandomAccessFile(indexFile, "rw");
        this.fileChannel = this.randomAccessFile.getChannel();
        // 不再立即映射文件
        // this.mappedBuffer = this.fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, maxSize);
        if (fileExisted) {
            recover();
        }
    }

    /**
     * 【新增】确保文件已被映射。这是一个线程安全的双重检查锁定模式。
     *
     * @throws IOException 如果映射失败
     */
    private void ensureMapped() throws IOException {
        if (mappedBuffer == null) {
            synchronized (mapLock) {
                if (mappedBuffer == null) {
                    this.mappedBuffer = this.fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, maxSize);
                }
            }
        }
    }

    private void recover() throws IOException {
        // 在恢复之前，必须确保文件已映射
        if (this.fileChannel.size() == 0) {
            this.entryCount.set(0);
            return;
        }
        ensureMapped();

        int position = 0;
        int count = 0;
        ByteBuffer buffer = this.mappedBuffer.asReadOnlyBuffer();
        long fileSize = this.fileChannel.size();
        while (position < fileSize && position <= buffer.limit() - IndexEntry.SIZE) {
            buffer.position(position);
            long offset = buffer.getLong();
            int pos = buffer.getInt();
            if ((offset == 0 && pos == 0 && count > 0) || offset < 0 || pos < 0) {
                log.info("Found end of valid index entries at position {}. Entry was (offset={}, pos={}). Breaking recovery.", position, offset, pos);
                break;
            }
            if (offset == 0 && pos == 0 && count == 0) {
                count++;
                position += IndexEntry.SIZE;
                continue;
            }
            if (offset == 0 && pos == 0) {
                log.info("Found end marker (0,0) at position {}. Breaking recovery.", position);
                break;
            }
            count++;
            position += IndexEntry.SIZE;
        }
        this.entryCount.set(count);
        log.info("从索引文件 {} 恢复了 {} 个条目。", indexFile.getName(), count);
    }

    public synchronized void append(long offset, int position) {
        if (closed.get()) {
            log.warn("索引文件 {} 已关闭，无法追加。", indexFile.getName());
            return;
        }
        try {
            ensureMapped();
        } catch (IOException e) {
            log.error("Failed to map index file on append for file {}", indexFile.getName(), e);
            return;
        }
        int currentPosition = entryCount.get() * IndexEntry.SIZE;
        if (currentPosition + IndexEntry.SIZE > maxSize) {
            log.error("索引文件 {} 已满。", indexFile.getName());
            return;
        }
        ((Buffer) mappedBuffer).position(currentPosition);
        mappedBuffer.putLong(offset);
        mappedBuffer.putInt(position);
        entryCount.incrementAndGet();
    }

    public synchronized IndexEntry lookup(long targetOffset) {
        if (entryCount.get() == 0) {
            return null;
        }
        try {
            ensureMapped();
        } catch (IOException e) {
            log.error("Failed to map index file on lookup for file {}", indexFile.getName(), e);
            return null;
        }

        int low = 0;
        int high = entryCount.get() - 1;
        ByteBuffer buffer = mappedBuffer.asReadOnlyBuffer();

        while (low <= high) {
            int mid = low + (high - low) / 2;
            buffer.position(mid * IndexEntry.SIZE);
            long midOffset = buffer.getLong();

            if (midOffset == targetOffset) {
                return new IndexEntry(midOffset, buffer.getInt());
            } else if (midOffset < targetOffset) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }

        int targetIndex = high;
        if (targetIndex < 0) {
            return null;
        }

        buffer.position(targetIndex * IndexEntry.SIZE);
        return new IndexEntry(buffer.getLong(), buffer.getInt());
    }

    public void flush() {
        // 只有在 mappedBuffer 存在时才刷新
        if (mappedBuffer != null) {
            mappedBuffer.force();
        }
    }

    public void truncateAndClear() {
        try {
            if (fileChannel != null && fileChannel.isOpen()) {
                fileChannel.truncate(0);
            }
            if (mappedBuffer != null) {
                mappedBuffer.position(0);
            }
            entryCount.set(0);
            log.debug("索引文件 {} 已被截断和清理。", indexFile.getName());
        } catch (IOException e) {
            log.error("截断索引文件 {} 失败。", indexFile.getName(), e);
        }
    }

    public void load() throws IOException {
        if (this.fileChannel != null && this.entryCount.get() == 0 && this.fileChannel.size() > 0) {
            // 不再简单地通过文件大小计算，而是调用恢复逻辑
            recover();
            log.info("Reloaded index file {}, found {} entries after recovery.", this.indexFile.getName(), this.entryCount.get());
        }
    }

    public boolean isEmpty() {
        return this.entryCount.get() == 0;
    }

    /**
     * 【新增】获取索引中的最后一条记录。
     *
     * @return 最后一个 IndexEntry 对象，如果索引为空则返回 null。
     */
    public synchronized IndexEntry getLastEntry() {
        if (isEmpty()) {
            return null;
        }
        // 在访问之前，必须确保文件已映射
        try {
            ensureMapped();
        } catch (IOException e) {
            log.error("Failed to map index file on getLastEntry for file {}", indexFile.getName(), e);
            return null;
        }
        int lastEntryIndex = this.entryCount.get() - 1;
        ByteBuffer buffer = mappedBuffer.asReadOnlyBuffer();
        buffer.position(lastEntryIndex * IndexEntry.SIZE);
        return new IndexEntry(buffer.getLong(), buffer.getInt());
    }

    /**
     * 【新增】检查索引文件是否已达到其最大容量。
     *
     * @return 如果已满，则返回 true。
     */
    public boolean isFull() {
        // 使用 long 类型进行比较，以避免潜在的整数溢出
        return (long) this.entryCount.get() * IndexEntry.SIZE >= this.maxSize;
    }

    @Override
    public void close() throws IOException {
        if (!closed.compareAndSet(false, true)) {
            return;
        }
        log.debug("正在关闭索引文件...");
        try {
            flush();
        } finally {
            if (mappedBuffer != null) {
                try {
                    BufferCleaner.clean(this.mappedBuffer);
                    this.mappedBuffer = null;
                } catch (Exception e) {
                    log.error("清理索引文件的 MappedByteBuffer 失败。", e);
                }
            }

            if (randomAccessFile != null) {
                try {
                    randomAccessFile.close();
                } catch (IOException e) {
                    log.error("关闭索引文件的 RandomAccessFile 失败。", e);
                }
            }
            // 帮助GC，并避免悬空引用
            this.fileChannel = null;

            ResourceManager.forceGcAndWait(50);
            log.debug("索引文件关闭完成。");
        }
    }

    /**
     * 【新增】返回当前索引文件中的条目总数。
     *
     * @return 索引条目数量
     */
    public int getEntryCount() {
        return this.entryCount.get();
    }
}